package com.imooc.utils;

import lombok.extern.slf4j.Slf4j;
import org.locationtech.proj4j.*;
import org.locationtech.proj4j.io.Proj4FileReader;

import java.io.IOException;

/**
 * @ProjectName: stdd-cmnt
 * @Package: com.chinaunicom.mo.smartcity.util
 * @ClassName: Proj4jUtils
 * @Description: 地图转换工具类
 * @Author: wangjc
 * @CreateDate: 2020/3/6 0006 12:54
 * @UpdateUser: 更新者
 * @UpdateDate: 2020/3/6 0006 12:54
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */
@Slf4j
public class Proj4jUtils {
    private static double dNorth;
    private static double dEast;
    private static double dHight;
    //定义一些常量
    private static final double PI = 3.1415926535897932384626;
    private static final double x_PI = 3.14159265358979324 * 3000.0 / 180.0;
    private static final double a = 6378245.0;
    private static final double ee = 0.00669342162296594323;

    private static CoordinateTransform transform;


    /**
     * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
     * 即 百度 转 谷歌、高德
     *
     * @returns {*[]}
     */
    public static MapCdntVO bd09togcj02(MapCdntVO vo) {
        double x = vo.getLongitude() - 0.0065;
        double y = vo.getLatitude() - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
        double gg_lng = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new MapCdntVO(gg_lng, gg_lat);
    }

    /**
     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
     * 即谷歌、高德 转 百度
     * @returns {*[]}
     */
    public static MapCdntVO gcj02tobd09(MapCdntVO vo){
        double z = Math.sqrt(vo.getLongitude() * vo.getLongitude() + vo.getLatitude() * vo.getLatitude()) + 0.00002 * Math.sin(vo.getLatitude() * x_PI);
        double theta = Math.atan2(vo.getLatitude(), vo.getLongitude()) + 0.000003 * Math.cos(vo.getLongitude() * x_PI);
        double bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new MapCdntVO(bd_lng, bd_lat);
    };

    /**
     * @return
     * @method: wg84To3857
     * @description: WGS84(4326)坐标转墨卡托(3857)的坐标
     * @date: 2020/3/6 0006 13:08
     * @author: wangjc
     * @param:
     */
    public static MapCdntVO wg84To3857(MapCdntVO vo) {
        return transCode(true, vo.getLongitude(), vo.getLatitude(), 0);
    }

    /**
     * @return
     * @method: c3857Towg84
     * @description: 墨卡托(3857)坐标转WGS84(4326)的坐标
     * @date: 2020/3/6 0006 13:10
     * @author: wangjc
     * @param:
     */
    public static MapCdntVO c3857Towg84(MapCdntVO vo) {
        return transCode(false, vo.getLongitude(), vo.getLatitude(), 0);
    }

    /**
     * WGS84转GCj02
     *
     * @param vo
     * @returns {*[]}
     */
    public static MapCdntVO wgs84togcj02(MapCdntVO vo) {

        double lng = vo.getLongitude();
        double lat = vo.getLatitude();
        if (!outOfChina(lng, lat)) {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = lat / 180.0 * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
            dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
            double mglat = vo.getLatitude() + dlat;
            double mglng = vo.getLongitude() + dlng;
            vo.setLongitude(mglng);
            vo.setLatitude(mglat);
        }
        return vo;
    }

    /**
     * GCJ02 转换为 WGS84
     *
     * @param vo
     * @returns {*[]}
     */
    public static MapCdntVO gcj02towgs84(MapCdntVO vo) {
        double lat = vo.getLatitude();
        double lng = vo.getLongitude();
        if (!outOfChina(lng, lat)) {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = lat / 180.0 * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
            dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
            double mglat = lat + dlat;
            double mglng = lng + dlng;
            vo.setLongitude(lng * 2 - mglng);
            vo.setLatitude(lat * 2 - mglat);
        }
        return vo;
    }

    /**
     * 判断是否在国内，不在国内则不做偏移
     *
     * @param lng
     * @param lat
     * @returns {boolean}
     */
    public static boolean outOfChina(double lng, double lat) {
        //        var lat = +lat;
        //        var lng = +lng;
        // 纬度3.86~53.55,经度73.66~135.05
        return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.5);
    }

    /**
     * 转换经度
     *
     * @param lng
     * @param lat
     * @return
     */
    public static double transformlat(double lng, double lat) {
        double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 转换纬度
     *
     * @param lng
     * @param lat
     * @return
     */
    public static double transformlng(double lng, double lat) {
        double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
        ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
        return ret;
    }


    private static String[] getParamForm(String authorityCode, String name) {
        String paramStr[] = null;
        Proj4FileReader proj4FileReader = new Proj4FileReader();
        try {
            paramStr = proj4FileReader.readParametersFromFile(authorityCode, name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return paramStr;
    }

    private static MapCdntVO transCode(boolean transFlag, double b, double l, double h) {
        CRSFactory targetFactory = new CRSFactory();
        //目标坐标系统
        CoordinateReferenceSystem target = targetFactory
                .createFromParameters("3857", getParamForm("epsg", "3857"));

        //源坐标系统
        CoordinateReferenceSystem wgs84 = targetFactory
                .createFromParameters("4326", getParamForm("epsg", "4326"));
        CoordinateTransformFactory transformFactory = new CoordinateTransformFactory();

        if (transFlag) {
            transform = transformFactory.createTransform(wgs84, target);
        } else {
            transform = transformFactory.createTransform(target, wgs84);
        }

        return transform(b, l, h);
    }

    /**
     * @return
     * @method: transform
     * @description: 坐标转换方法
     * @date: 2020/3/6 0006 13:15
     * @author: wangjc
     * @param:
     */
    private static MapCdntVO transform(double x, double y, double z) {
        if (transform != null) {
            ProjCoordinate projCoordinate = new ProjCoordinate(x, y);
            ProjCoordinate sourceProj = new ProjCoordinate();
            transform.transform(projCoordinate, sourceProj);
            dNorth = sourceProj.x;
            dEast = sourceProj.y;
            dHight = sourceProj.z;
        } else {
            log.error("no transform create");
        }
        log.debug("x:{}, y:{}, z:{}", dNorth, dEast, dHight);
        return new MapCdntVO(dNorth, dEast);
    }
}
